package com.soccertgc.core.game.engine
{
	
	import com.soccertgc.core.Player;
	import com.soccertgc.core.User;
	import com.soccertgc.core.cards.Deck;
	import com.soccertgc.core.game.Game;
	import com.soccertgc.core.game.GameCards;
	import com.soccertgc.core.game.GameEndCause;
	import com.soccertgc.core.game.GameFormation;
	import com.soccertgc.core.game.GameOptions;
	import com.soccertgc.core.game.GamePhase;
	import com.soccertgc.core.game.GameServerStatus;
	import com.soccertgc.core.game.GameStatus;
	import com.soccertgc.core.game.GameTurn;
	import com.soccertgc.core.game.client.GameClient;
	import com.soccertgc.core.game.client.events.GameClientErrorEvent;
	import com.soccertgc.core.game.client.events.GameClientEvent;
	import com.soccertgc.core.game.client.events.GameClientRequestEvent;
	import com.soccertgc.core.game.engine.events.GameEngineErrorEvent;
	import com.soccertgc.core.game.engine.events.GameEngineEvent;
	import com.soccertgc.core.game.events.GameEndEvent;
	import com.soccertgc.core.game.events.GameErrorEvent;
	import com.soccertgc.core.game.events.GameEvent;
	import com.soccertgc.core.game.events.GameNextTurnEvent;
	import com.soccertgc.core.game.events.GamePhaseErrorEvent;
	import com.soccertgc.core.game.events.GamePhaseEvent;
	import com.soccertgc.core.game.events.GamePhaseProgressEvent;
	import com.soccertgc.core.game.events.GamePhaseTimeoutEvent;
	import com.soccertgc.core.game.events.GameResolveDiscardPhaseEvent;
	import com.soccertgc.core.game.events.GameResolvePlayPhaseEvent;
	import com.soccertgc.core.game.events.GameResolveSubstitutionPhaseEvent;
	import com.soccertgc.core.game.requests.GameRequest;
	import com.soccertgc.core.game.requests.GameRequestFactory;
	import com.soccertgc.core.game.requests.GameRequestParams;
	import com.soccertgc.logging.Logger;
	import com.soccertgc.utils.DateUtils;
	import com.soccertgc.utils.JsonUtils;
	import com.soccertgc.utils.StringUtils;
	import com.soccertgc.utils.Utils;
	
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	public class GameEngine extends EventDispatcher
	{
	
		// -------------------------------------------------------------------------------
		// CONSTANTS
		// -------------------------------------------------------------------------------
		
		// Logging
		private static const		LOG_MARKER:String								= "ENGINE > ";
		private static const		LOG_MARKER_CLIENT:String						= LOG_MARKER + "CLIENT > ";
		private static const		LOG_MARKER_GAME:String							= LOG_MARKER + "GAME > ";
		private static const		LOG_MARKER_WORKER:String						= LOG_MARKER + "WORKER > ";
		private static const		LOG_MARKER_MATCHMAKING:String					= LOG_MARKER + "MATCH MAKING > ";
		private static const		LOG_MARKER_TIMEOUT:String						= LOG_MARKER + "TIMEOUT > ";
		
		// Debug keys
		public static const			DEBUG_KEY:String								= "core.game.engine";
		public static const			DEBUG_KEY_WORKER:String							= "core.game.engine.worker";
		public static const			DEBUG_KEY_GAME:String							= "core.game.engine.client";
		public static const			DEBUG_KEY_TIMEOUT:String						= "core.game.engine.timeout";
		public static const			DEBUG_KEY_CLIENT:String							= "core.game.engine.game";
		public static const			DEBUG_KEY_MATCHMAKING:String					= "core.game.engine.matchmacking";
		
		// JSON properties names		
		protected static const		JSON_PROPERTY_DISCARD_CARD_LOG:String			= "cardLog";
		protected static const		JSON_PROPERTY_DISCARD_PLAYER_HAND:String		= "playerHand";
		protected static const		JSON_PROPERTY_DISCARD_OPPONENT_HAND:String		= "opponentHand";
		protected static const		JSON_PROPERTY_SUBSTITUTION_RESYNC:String		= "resync";
		protected static const		JSON_PROPERTY_ROUND_STATUS:String				= "roundStatus";
		
		// Delays
		private static const		WORKER_DELAY:uint								= 0050; // 0.1sec
		
		// -------------------------------------------------------------------------------
		// FIELDS
		// -------------------------------------------------------------------------------
		
		private var _user:User;
		private var _deck:Deck;
		private var _game:Game;
		private var _options:GameOptions;
		private var _client:GameClient;
		private var _worker:Timer;
		
		private var _status:GameEngineStatus;
		private var _previousStatus:GameEngineStatus;
		
		private var _timeoutActive:Boolean;
		private var _timeoutPhase:GamePhase;
		private var _timeoutElapsed:Number;
		private var _timeoutLastDate:Date;
		
		// -------------------------------------------------------------------------------
		// CONSTRUCTOR
		// -------------------------------------------------------------------------------
		
		public function GameEngine(client:GameClient, options:GameOptions)
		{
			// Parameters check
			if (client == null) throw new Error("GameClient should no t be null");
			if (options == null) throw new Error("GameOptions should no t be null");
			// Init objects
			_options = options;
			// Init timer
			_worker = new Timer(WORKER_DELAY, 0); // 0 = infinite
			_worker.addEventListener(TimerEvent.TIMER, workerHandler);
			// Init client
			_client = client;
			_client.addEventListener(GameClientEvent.EVENT_CONNECT, 			client_ConnectHandler);
			_client.addEventListener(GameClientEvent.EVENT_DISCONNECT, 			client_DisconnectHandler);
			_client.addEventListener(GameClientEvent.EVENT_CONNECTION_FAILED,	client_ConnectionFailedHandler);
			_client.addEventListener(GameClientEvent.EVENT_CONNECTION_LOST,		client_ConnectionLostHandler);
			_client.addEventListener(GameClientEvent.EVENT_REQUEST_EXECUTED,	client_RequestExecutedHandler);
			_client.addEventListener(GameClientEvent.EVENT_REQUEST_WAITING,		client_RequestWaitingHandler);
			_client.addEventListener(GameClientEvent.EVENT_REQUEST_FAILED,		client_RequestFailedHandler);
			_client.addEventListener(GameClientEvent.EVENT_ERROR,				client_ErrorHandler);
			// STATUS > IDLE
			_status = GameEngineStatus.IDLE;
		}
		
		
		// -------------------------------------------------------------------------------
		// PROPERTIES
		// -------------------------------------------------------------------------------

		public function get status():GameEngineStatus
		{
			return _status;
		}
		
		public function get client():GameClient
		{
			return _client;
		}

		public function get game():Game
		{
			return _game;
		}
		
		public function get hasGame():Boolean
		{
			return _game != null;
		}

		public function get isPaused():Boolean
		{
			return _status == GameEngineStatus.PAUSED;
		}
		
		public function get isRunning():Boolean
		{
			return _status == GameEngineStatus.RUNNING;
		}
		
		public function get isMatchmaking():Boolean
		{
			return _status == GameEngineStatus.MATCHMAKING;
		}
		
		public function get isPlaying():Boolean
		{
			return _status == GameEngineStatus.PLAYING;
		}
		
		
		// -------------------------------------------------------------------------------
		// PRIVATE METHODS
		// -------------------------------------------------------------------------------
		
		// Engine handling
		
		protected function setStatus(newStatus:GameEngineStatus):void
		{
			if (_status != newStatus)
			{
				var oldStatus:GameEngineStatus = _status;
				// Save the status
				_status = newStatus;
				_previousStatus = oldStatus;
				// DEBUG > Trace status change
				if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "STATUS > changed {0} to {1}", oldStatus, newStatus);
			}
		}
				
		protected function manageClientError(message:String, error:Error = null):void
		{
			// Compose the error message
			var msg:String = LOG_MARKER_CLIENT + "FATAL ERROR";
			if (!StringUtils.isEmpty(message))
			{
				msg += " > " + message;
			}
			// ERROR > log the complete error
			Logger.error(msg, error);
			// Custom error object
			var e:Error = error != null ? error : new Error(message);
			// Check if we are in matchmaking
			if (_status == GameEngineStatus.MATCHMAKING)
			{
				// LT_TODO: gestire errore di fatale in matchmacking
			}
			// Check if the game is running
			if (_status == GameEngineStatus.PLAYING)
			{
				if (!_game.isInError) gameError(e);
			}
			// Stops the engine
			stop();
		}
		
		protected function manageGameError(message:String, error:Error = null):void
		{
			// Compose the error message
			var msg:String = LOG_MARKER_GAME + "FATAL ERROR";
			if (!StringUtils.isEmpty(message))
			{
				msg += " > " + message;
			}
			// ERROR > log the complete error
			Logger.error(msg, error);
			// Custom error object
			var e:Error = error != null ? error : new Error(message);
			// Forces the game error
			gameError(e);
			// Dispatch EVENT_GAME_ENGINE_ERROR event
			dispatchEngineErrorEvent(message, e);
			// Stops the engine
			stop();
		}
		
		protected function manageFatalError(message:String, error:Error):void
		{
			
			// Compose the error message
			var msg:String = LOG_MARKER + "FATAL ERROR";
			if (!StringUtils.isEmpty(message))
			{
				msg += " > " + message;
			}
			// ERROR > log the complete error
			Logger.error(msg, error);
			// Custom error object
			var e:Error = error != null ? error : new Error(message);
			// Check if we are in matchmaking
			if (_status == GameEngineStatus.MATCHMAKING)
			{
				// LT_TODO: gestire errore di fatale in matchmacking
			}
			// Check if the game is running
			if (_status == GameEngineStatus.PLAYING)
			{
				// Forces the game error
				if (!_game.isInError) gameError(e);
			}
			// Dispatch EVENT_GAME_ENGINE_ERROR event
			dispatchEngineErrorEvent(message, e);
			// Stops the engine
			stop();
		}
		
		
		// Game status handling
		
		protected function gameCheckDesync(game:Game):Boolean
		{
			try
			{
				// Desync flag
				var desync:Boolean = false;				
				// Server returns the next turn
				if (!desync && _game.turn != (game.turn - 1))
				// if (!desync && _game.turn != game.turn)
				{
					desync = true;
					// ERROR > Game desync !!!
					Logger.error(LOG_MARKER_GAME + " !!! DESYNC !!! client turn ({0}) and server turn ({1}) mismatch", _game.turn, game.turn);
				}
				
				// LT_TODO: implementare gestione Game desync
				
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error checking Game desync", e);
			}
			return !desync;
		}
		
		protected function gameCheckPhaseTimeout():Boolean
		{
			
			// LT_TODO: implementare controllo su tempo trascorso da invio richiesta
			
			return false;
		}
		
		protected function gameReady(readyData:Object):void
		{
			try
			{
				// Check if the current status is UNKNOWN
				if (_game.status == GameStatus.UNKNOWN || _game.status == GameStatus.READY)
				{
					// LT_TODO: parse raw data cha arrivano dalla fase di matchmacking
					
					// LT_TODO: esce dalla fase di matchmacking
					
					// LT_TODO: imposta un po di dati della propria squadra
					
					// DEBUG > Game ready, matchmacking end
					if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "game ready!");
				}
				else
				{
					// Desync happened
					gameDesyncByStatus("GameEngine.ready()", GameStatus.UNKNOWN);
				}
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error handling Game redy", e);
			}
		}
		
		protected function gameStart(startData:Object):Boolean // returns true if after the game start the first request must be handled
		{
			// Game started flag
			var requestToHandle:Boolean = true;
			//
			try
			{
				// Check if the current status is READY
				if (_game.status == GameStatus.READY)
				{
					// Check that the result status is provided
					if (!startData.hasOwnProperty(JSON_PROPERTY_ROUND_STATUS)) manageGameError("Error restarting a Game: " + JSON_PROPERTY_ROUND_STATUS + "propery is missing!");
					// Retrieve the server game status
					var serverStatus:GameServerStatus = GameServerStatus.getById(uint(startData[JSON_PROPERTY_ROUND_STATUS]));
					// Fa un resync completo dei dati
					_game.load(startData);
					// DEBUG > Game started
					if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "game started, first resync done!");
					// Check if the loaded game is new and must be started
					if (_game.isFirstTurn && serverStatus == GameServerStatus.WAITING)
					{
						// Dispatch EVENT_GAME_START event
						dispatchGameStart();
						// Calc the current game phase
						gameNextTurnPhase();
						// External request handling
						requestToHandle = true;
					}
					else
					{
						// Dispatch EVENT_GAME_RESTART event
						dispatchGameRestart();
						// Restart a game, with game status resync and auto request 
						gameRestart(serverStatus);
						// Already handled internally the first phase request
						requestToHandle = false;
					}
				}
				else
				{
					// Desync happened
					gameDesyncByStatus("GameEngine.gameStart()", GameStatus.READY);
					// The next request must not be handled
					requestToHandle = false;
				}
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error starting new Game", e);
				// The next request must not be handled
				requestToHandle = false;
			}
			return requestToHandle;
		}
		
		protected function gameRestart(serverStatus:GameServerStatus):void
		{
			try
			{
				// Game status force
				var status:GameStatus = GameStatus.UNKNOWN;
				// Request
				var request:GameRequest = null;
				// Check if the current status is READY
				if (_game.status == GameStatus.READY)
				{
					switch(serverStatus)
					{
						
						case GameServerStatus.WAITING:
							// Force game status to GameStatus.PLAY
							status = GameStatus.PLAY;
							// DEBUG > Next status
							if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "restart, forced status {0}", status);
							// Sets game status
							_game.changeStatus(status);
							// Dispatch EVENT_GAME_NEXT_TURN_PHASE event
							dispatchGameNextTurnPhase(GamePhase.getByGameStatus(status));
							break;
												
						case GameServerStatus.DISCARD:
							// Force game status to GameStatus.PLAY
							status = GameStatus.DISCARD;
							// DEBUG > Next status
							if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "restart, forced status {0}", status);
							// Sets game status
							_game.changeStatus(status);
							// Dispatch EVENT_GAME_NEXT_TURN_PHASE event
							dispatchGameNextTurnPhase(GamePhase.getByGameStatus(status));
							break;
						
						case GameServerStatus.SUBSTITUTION:
							// Force game status to GameStatus.PLAY
							status = GameStatus.SUBSTITUTION;
							// DEBUG > Next status
							if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "restart, forced status {0}", status);
							// Sets game status
							_game.changeStatus(status);
							// Dispatch EVENT_GAME_NEXT_TURN_PHASE event
							dispatchGameNextTurnPhase(GamePhase.getByGameStatus(status));
							break;
						
						case GameServerStatus.CALCULATING: // SOLVE_LOCK
						case GameServerStatus.SOLVE_LOCK:							
							// Force game status to GameStatus.RESOLVE_PLAY
							status = GameStatus.RESOLVE_PLAY;
							// DEBUG > Next status
							if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "restart, forced status {0}", status);
							// Sets game status
							_game.changeStatus(status);
							// Dispatch EVENT_GAME_NEXT_TURN_PHASE event
							dispatchGameNextTurnPhase(GamePhase.getByGameStatus(status));
							// Create the automatic request 
							request= createRequest(GamePhase.getByGameStatus(status));
							// DEBUG > Force to execute polling request
							if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "restart, request {0} execution forced", request);
							// Automatic execution
							execute(request);
							break;
						
						case GameServerStatus.DISCARD_POLLING:
						case GameServerStatus.DISCARD_LOCK:
							// Force game status to GameStatus.RESOLVE_DISCARD
							status = GameStatus.RESOLVE_DISCARD;
							// DEBUG > Next status
							if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "restart, forced status {0}", status);
							// Sets game status
							_game.changeStatus(status);
							// Dispatch EVENT_GAME_NEXT_TURN_PHASE event
							dispatchGameNextTurnPhase(GamePhase.getByGameStatus(status));
							// Create the automatic request 
							request = createRequest(GamePhase.getByGameStatus(status));
							// DEBUG > Force to execute polling request
							if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "restart, request {0} execution forced", request);
							// Automatic execution
							execute(request);
							break;
						
						case GameServerStatus.SUBSTITUTION_POLLING:
						case GameServerStatus.SUBSTITUTION_LOCK:
							// Force game status to GameStatus.RESOLVE_SUBSTITUTION
							status = GameStatus.RESOLVE_SUBSTITUTION;
							// DEBUG > Next status
							if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "restart, forced status {0}", status);
							// Sets game status
							_game.changeStatus(status);
							// Dispatch EVENT_GAME_NEXT_TURN_PHASE event
							dispatchGameNextTurnPhase(GamePhase.getByGameStatus(status));
							// Create the automatic request 
							request = createRequest(GamePhase.getByGameStatus(status));
							// DEBUG > Force to execute polling request
							if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "restart, request {0} execution forced", request);
							// Automatic execution
							execute(request);
							break;
						
						case GameServerStatus.COMPLETED:
							// VErifico se siamo all'ultimo turno
							if (_game.isLastTurn) manageGameError("Game restart failed, game is already completed!");
							else
							{
								
								// LT_TODO: da verificare
								
								// Manages the turn end and goes to the next turn
								gameNextTurn();
							}
							break;
						
						case GameServerStatus.UNKNOWN:
							// Game restart failed
							manageGameError("Game restart failed, UNKNOWN server game status");
							break; 
					}
				}
				else
				{
					// Desync happened
					gameDesyncByStatus("GameEngine.gameRetart()", GameStatus.READY);
				}
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error restarting an already started Game", e);
			}
		}
		
		protected function gameSubstitutionPhase(substitutionData:Object):void
		{
			try
			{
				// Check if the current status is SUBSTITUTION
				if (_game.status == GameStatus.SUBSTITUTION)
				{
					// Dispatch EVENT_GAME_SUBSTITUTION_PHASE event
					dispatchGameSubstitutionPhase();
					// Next turn phase
					gameNextTurnPhase();
					
					// Change game status to DISCARD
					// _game.changeStatus(GameStatus.RESOLVE_SUBSTITUTION);
					// Dispatch EVENT_GAME_ENGINE_PLAY_PHASE_CHANGED event
					// dispatchEnginePlayPhaseChangedEvent(GamePhase.getByGameStatus(GameStatus.RESOLVE_SUBSTITUTION));
				}
				else
				{
					// Desync happened
					gameDesyncByStatus("GameEngine.gameSubstitutionPhase()", GameStatus.SUBSTITUTION);
				}
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error handling SUBSTITUTION phase of Game turn", e);
			}
		}
		
		protected function gameResolveSubstitutionPhase(substitutionData:Object):void
		{
			try
			{
				// Check if the current status is RESOLVE_SUBSTITUTION
				if (_game.status == GameStatus.RESOLVE_SUBSTITUTION)
				{
					// Verify returned data
					if (substitutionData.hasOwnProperty(JSON_PROPERTY_SUBSTITUTION_RESYNC))
					{
						
						// LT_TODO: trasformare in un resync normale!
						/*
						// Creates a temp game to check the sync with the current status
						var game:Game = new Game(_game.player, game.options.clone(), _game.deck);
						game.load(resyncData);
						// Check if the game is desync with server
						if (gameCheckDesync(game))
						{
							// Updates/ Reload the status
							_game.load(resyncData);
							// Manages the turn end and goes to the next turn
							gameNextTurn();
						}
						else
						{
							// Game is desync, force desync status
							_game.changeStatus(GameStatus.DESYNC);
							// Dispatch EVENT_GAME_DESYNC event
							dispatchGameDesync();
						}
						}
						else
						{
							// Desync happened
							gameDesyncByStatus("GameEngine.gameResyncPhase()", GameStatus.RESYNC);
						}
						*/
						
						// Creates a temp game to check the sync with the current status
						var game:Game = new Game(_game.player, game.options.clone(), _game.deck);
						game.load(substitutionData[JSON_PROPERTY_SUBSTITUTION_RESYNC]);
						// Updates the formations
						_game.updateFormations(game.playerTeam.formation, game.opponentTeam.formation);
						// Dispaatch EVENT_GAME_RESOLVE_DISCARD_PHASE event
						dispatchGameResolveSubstitutionPhase(game.playerTeam.formation, game.opponentTeam.formation);
						// Next turn phase
						gameNextTurnPhase();
						
						// // Change game status to PLAY 
						// _game.changeStatus(GameStatus.DISCARD);
						// // Dispatch EVENT_GAME_ENGINE_PLAY_PHASE_CHANGED event
						// dispatchEnginePlayPhaseChangedEvent(GamePhase.getByGameStatus(GameStatus.DISCARD));
					}
					else
					{
						// ERROR > Missing RESOLVE_DISCARD return data
						manageGameError("RESOLVE_SUBSTITUTION return data doesn't contains " + JSON_PROPERTY_SUBSTITUTION_RESYNC + " field");
					}
				}
				else
				{
					// Desync happened
					gameDesyncByStatus("GameEngine.gameResolveSubstitutionPhase()", GameStatus.RESOLVE_SUBSTITUTION);
				}
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error handling RESOLVE_SUBSTITUTION phase of Game turn", e);
			}
		}
		
		protected function gameDiscardPhase(discardData:Object):void
		{
			try
			{
				// Check if the current status is DISCARD
				if (_game.status == GameStatus.DISCARD)
				{
					// Dispaatch EVENT_GAME_DISCARD_PHASE event
					dispatchGameDiscardPhase();
					// Next turn phase
					gameNextTurnPhase();
					
					// // Change game status to RESOLVE_DISCARD
					// _game.changeStatus(GameStatus.RESOLVE_DISCARD);
					// // Dispatch EVENT_GAME_ENGINE_PLAY_PHASE_CHANGED event
					// dispatchEnginePlayPhaseChangedEvent(GamePhase.getByGameStatus(GameStatus.RESOLVE_DISCARD));
				}
				else
				{
					// Desync happened
					gameDesyncByStatus("GameEngine.gameDiscardPhase()", GameStatus.DISCARD);
				}
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error handling DISCARD phase of Game turn", e);
			}
		}

		protected function gameResolveDiscardPhase(discardData:Object):void
		{
			try
			{
				// Check if the current status is RESOLVE_DISCARD
				if (_game.status == GameStatus.RESOLVE_DISCARD)
				{
					// Verify data returned
					if (discardData.hasOwnProperty(JSON_PROPERTY_DISCARD_CARD_LOG))
					{
						// Retrieve raw data
						var rawCardLog:Object = discardData[JSON_PROPERTY_DISCARD_CARD_LOG];
						// Verify data returned
						if (rawCardLog.hasOwnProperty(JSON_PROPERTY_DISCARD_PLAYER_HAND) || rawCardLog.hasOwnProperty(JSON_PROPERTY_DISCARD_OPPONENT_HAND))
						{
							// Raw data
							var rawPlayerCards:Object 	= rawCardLog[JSON_PROPERTY_DISCARD_PLAYER_HAND];
							var rawOpponentCards:Object = rawCardLog[JSON_PROPERTY_DISCARD_OPPONENT_HAND];
							// Decode player cards data
							var playerCards:GameCards = _game.playerTeam.cards.duplicate();
							playerCards.load(rawPlayerCards);
							// Decode player cards data
							var opponentCards:GameCards = _game.opponentTeam.cards.duplicate();
							opponentCards.load(rawPlayerCards);
							// Updates new data to the game
							_game.updateCards(playerCards, opponentCards);
							// Dispaatch EVENT_GAME_RESOLVE_DISCARD_PHASE event
							dispatchGameResolveDiscardPhase(playerCards, opponentCards);
							// Next turn phase
							gameNextTurnPhase();
							
							// // Change game status to PLAY 
							// _game.changeStatus(GameStatus.PLAY);
							// // Dispatch EVENT_GAME_ENGINE_PLAY_PHASE_CHANGED event
							// dispatchEnginePlayPhaseChangedEvent(GamePhase.getByGameStatus(GameStatus.PLAY));
						}
						else
						{
							// ERROR > Missing RESOLVE_DISCARD return data
							manageGameError("RESOLVE_DISCARD return data doesn't contains one or more cards data fields");							
						}
					}
					else
					{
						// ERROR > Missing RESOLVE_DISCARD return data
						manageGameError("RESOLVE_DISCARD return data doesn't contains " + JSON_PROPERTY_DISCARD_CARD_LOG + " field");
					}
				}
				else
				{
					// Desync happened
					gameDesyncByStatus("GameEngine.gameResolveDiscardPhase()", GameStatus.RESOLVE_DISCARD);
				}
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error handling RESOLVE_DISCARD phase of Game turn", e);
			}
		}
		
		protected function gamePlayPhase(playData:Object):void
		{
			try
			{
				// Check if the current status is PLAY
				if (_game.status == GameStatus.PLAY)
				{
					// Dispaatch EVENT_GAME_PLAY_PHASE event
					dispatchGamePlayPhase();
					// Next turn phase
					gameNextTurnPhase();
					
					// // Change game status to RESOLVE, waiting opponent to play and server to send turn resolving data
					// _game.changeStatus(GameStatus.RESOLVE_PLAY);
					// // Dispatch EVENT_GAME_ENGINE_PLAY_PHASE_CHANGED event
					// dispatchEnginePlayPhaseChangedEvent(GamePhase.getByGameStatus(GameStatus.RESOLVE_PLAY));
				}
				else
				{
					// Desync happened
					gameDesyncByStatus("GameEngine.gamePlayPhase()", GameStatus.PLAY);
				}
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("GAME > unexpected error handling PLAY phase of Game turn", e);
			}
		}

		protected function gameResolvePlayPhase(resolveData:Object):void
		{
			try
			{
				// Check if the current status is RESOLVE_PLAY
				if (_game.status == GameStatus.RESOLVE_PLAY)
				{
					// Creates game turn
					var turn:GameTurn = new GameTurn(resolveData, _game.side, _game.playerTeam.role);
					// Save game turn data
					game.updateTurns(turn);
					// Dispaatch EVENT_GAME_RESOLVE_PLAY_PHASE event
					dispatchGameResolvePlayPhase(turn);
					// Next turn phase
					gameNextTurnPhase();
					
					// // Change game status to RESYNC 
					// _game.changeStatus(GameStatus.RESYNC);
					// // Dispatch EVENT_GAME_ENGINE_PLAY_PHASE_CHANGED event
					// dispatchEnginePlayPhaseChangedEvent(GamePhase.getByGameStatus(GameStatus.RESYNC));
				}
				else
				{
					// Desync happened
					gameDesyncByStatus("GameEngine.gameResolvePlayPhase()", GameStatus.RESOLVE_PLAY);
				}
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error handling RESOLVE_PLAY phase of Game turn", e);
			}
		}
				
		protected function gameResyncPhase(resyncData:Object):void
		{
			try
			{
				// Check if the current status is RESYNC
				if (_game.status == GameStatus.RESYNC)
				{
					// Creates a temp game to check the sync with the current status
					var game:Game = new Game(_game.player, game.options.clone(), _game.deck);
					game.load(resyncData);
					// Check if the game is desync with server
					if (gameCheckDesync(game))
					{
						// Updates/ Reload the status
						_game.load(resyncData);
						// Manages the turn end and goes to the next turn
						gameNextTurn();
					}
					else
					{
						// Game is desync, force desync status
						_game.changeStatus(GameStatus.DESYNC);
						// Dispatch EVENT_GAME_DESYNC event
						dispatchGameDesync();
					}
				}
				else
				{
					// Desync happened
					gameDesyncByStatus("GameEngine.gameResyncPhase()", GameStatus.RESYNC);
				}
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error in Game turn end", e);
			}
		}
		
		protected function gameResync(gameData:Object):void
		{
			try
			{
				// Check if the current status is DESYNC
				if (_game.status == GameStatus.DESYNC)
				{
					// Reload all the state
					_game.load(gameData);
					// DEBUG > Game full resync
					if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "game full resync (data:{1})", JsonUtils.encode(gameData));
					// Dispatch EVENT_GAME_RESYNC event
					dispatchGameResync();
				}
				else
				{
					// Desync happened
					gameDesyncByStatus("GameEngine.gameResync()", GameStatus.DESYNC);
				}	
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error in Game resync", e);
			}
		}
				
		protected function gameEnd(cause:GameEndCause):void
		{
			try
			{
				// DEBUG > Game full resync
				if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "game end! (cause:{0})", cause);
				// STATUS > DONE, game end
				_game.changeStatus(GameStatus.DONE);
				// Game end!
				dispatchGameEnd(cause);
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error in Game end", e);
			}
		}
		
		protected function gameNextRequest():void
		{
			// Retrieve the current GamePhase
			var phase:GamePhase = GamePhase.getByGameStatus(_game.status);
			if (phase != null)
			{
				// Check if the current phase is passive
				if (!phase.active)
				{
					// Create the automatic request 
					var request:GameRequest = createRequest(phase);
					// DEBUG > Next request, passive
					if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "next request, PASSIVE : execute next request {0}", request);
					// Automatic execution
					execute(request);
				}
				else
				{
					// Starts the timeout for the current request
					timeoutStart(phase);
					// DEBUG > Next request, active
					if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "next request, ACTIVE : timeout ({0} ms) started", _options.getGamePhaseTimeout(phase));
				}
			}
			else
			{
				// ERROR > Try to manage passive request in the wrong GameStatus
				manageGameError("Try to manage unknown next request in the wrong GameStatus " + _game.status);
			}
		}
		
		protected function gameNextTurnPhase():void
		{
			var status:GameStatus = null;
			switch (_game.status)
			{
				
				case GameStatus.SUBSTITUTION:			status = GameStatus.RESOLVE_SUBSTITUTION; 	break;
				case GameStatus.RESOLVE_SUBSTITUTION:	status = GameStatus.DISCARD; 				break;
				case GameStatus.DISCARD:				status = GameStatus.RESOLVE_DISCARD; 		break;
				case GameStatus.RESOLVE_DISCARD:		status = GameStatus.PLAY;					break;
				case GameStatus.PLAY:					status = GameStatus.RESOLVE_PLAY; 			break;
				case GameStatus.RESOLVE_PLAY: 			status = GameStatus.RESYNC; 				break;
				case GameStatus.READY:
				case GameStatus.RESYNC:
				{
					// Check if is the first game turn
					if (_game.isFirstTurn)
					{
						// STATUS > Skip substitutuon and discard, start to play
						status = GameStatus.PLAY;
						// DEBUG > Next status, first turn
						if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "next phase: first turn!");
					}
					// Check if is half-time
					else if (_game.isHalfTime)
					{
						// STATUS > Player substitution
						status = GameStatus.SUBSTITUTION;
						// DEBUG > Next status, half time
						if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "next phase: half time");
					}
					// Other turns
					else
					{
						// STATUS > Hand card discard
						status = GameStatus.DISCARD;
					}
					break;
				}
				default:
				{
					// ERROR > Can't change phase when in wrong status
					manageGameError("Next phase called with an invalid GameStatus: " + status);
					break;
				}
			}
			// Apply next phase status
			if (status != null)
			{
				// DEBUG > Next status
				if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "next phase: from {0} to {1}", _game.status, status);
				// Set game status
				_game.changeStatus(status);
				// new phase
				var phase:GamePhase = GamePhase.getByGameStatus(status);
				// Dispatch EVENT_GAME_NEXT_TURN_PHASE event
				dispatchGameNextTurnPhase(phase);
			}
		}
		
		protected function gameNextTurn():void
		{
			try
			{
				// Check if is the last game turn
				if (!_game.isLastTurn)
				{
					// DEBUG > Next status
					if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "next turn");
					// Next phase (depends on turn and game status)
					gameNextTurnPhase();
					// Dispatch next turn event
					dispatchGameNextTurn(_game.turn);
				}
				else
				{
					// Game as ended by playing, check who wins
					var gameEndCause:GameEndCause = _game.isPlayerWinning ? GameEndCause.PLAYER_WIN : GameEndCause.OPPONENT_WIN;
					// END GAME!
					gameEnd(gameEndCause);
				}
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error in game next Turn handling", e);
			}
		}
		
		protected function gameDesync(message:String):void
		{
			try
			{
				// DEBUG > !!! DESYNC !!!
				Logger.error(LOG_MARKER_GAME + "<GAME_DESYNC> (game:{0}) : {1}", Utils.coalesce(_game.id), message);
				// Set the status
				_game.changeStatus(GameStatus.DESYNC);
				// Dispatch EVENT_GAME_ERROR event
				dispatchGameDesync();
				
				// LT_TODO: valutare se provare a fare un nuovo resyn, non un resync request ma una gestione
				// apposita, attualmente manda l'engine in errore e ferma tutto
				
				// Force a fata error
				manageGameError("Game desync!");
			}
			catch (e:Error)
			{
				// Unexpected error handling
				manageGameError("unexpected error in game desync handling", e);
			}
		}
		
		protected function gameDesyncByStatus(method:String, requiredStatus:GameStatus):void
		{
			// Force desync for worng status handling
			gameDesync("Method " + method + " requires status "+ requiredStatus + " but engine is currently in status " + _status);
		}
				
		protected function gameError(error:Error):void
		{
			// DEBUG > !!! DESYNC !!!
			Logger.error(LOG_MARKER_GAME + "<GAME_ERROR> (game:{0}, status:{1})", Utils.coalesce(_game.id), _game.status);
			// Stops any running timeout check
			timeoutStop();
			// Stops any request
			_client.cancel();
			// GAME > Set the status
			_game.changeStatus(GameStatus.ERROR);
			// Dispatch EVENT_GAME_ERROR event
			dispatchGameError(error);
		}
		
		
		// Game phase handlign
		
		protected function phaseExecuted(request:GameRequest):void
		{
			// Flag to enable next request handling
			var manageNextRequest:Boolean = true; 
			// Safe exec
			try
			{
				// DEBUG > Phase request executed with success
				if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "{0} executed (result:{1}, data:{2})", request, request.result.code, JsonUtils.encode(request.result.data));
				// Check if the current action 
				if (request.phase.active)
				{
					// Resets the timeout
					timeoutStop();
				}
				// Check if the game is already started (has a valid turn)
				if (!_game.isStarted)
				{
					// Check that the first request is a RESYNC
					if (request.phase == GamePhase.RESYNC)
					{
						// Check request success
						if (!request.isInError)
						{
							// Process the game start
							manageNextRequest = gameStart(request.result.data);
						}
						else
						{
							// ERROR > First RESYNC can't be in error, so generate a fatal game error
							manageGameError("First game request RESYNC in error : " + request.result.error.message + "(" + request.result.error.code + ")");
							// Next request not to be handled
							manageNextRequest = false;
						}						
					}
					else
					{
						// ERROR > First request not for GamePhase.RESYNC
						manageGameError("First game request bust be a " + GamePhase.RESYNC + " not a {1} " + request.phase);
						// Next request not to be handled
						manageNextRequest = false;
					}
				}
				// Check if the game is ended by the server
				else if (request.result.isInGameEnd)
				{
					// End game by request response by server
					phaseExecutedWithEndGame(request);
					// Next request not to be handled
					manageNextRequest = false;
				}
				else
				{
					// Phase base handling
					switch (request.phase)
					{
						case GamePhase.SUBSTITUTE:
						{
							gameSubstitutionPhase(request.result.data);
							break;
						}
						case GamePhase.RESOLVE_SUBSTITUTE:
						{
							gameResolveSubstitutionPhase(request.result.data);
							break;
						}
						case GamePhase.DISCARD:
						{
							gameDiscardPhase(request.result.data);
							break;
						}
						case GamePhase.RESOLVE_DISCARD:
						{
							gameResolveDiscardPhase(request.result.data);
							break;
						}
						case GamePhase.PLAY:
						{
							gamePlayPhase(request.result.data);
							break;
						}
						case GamePhase.RESOLVE_PLAY:
						{
							gameResolvePlayPhase(request.result.data);
							break;
						}
						case GamePhase.RESYNC:
						{
							gameResyncPhase(request.result.data);
							break;
						}
					}
				}
				// If enabled, manage next request
				if (manageNextRequest) gameNextRequest();
			} 
			catch(error:Error) 
			{
				// ERROR > Fatal unexpected error
				manageGameError(LOG_MARKER_GAME + "fatal unexpected error in phaseExecuted()", error);
			}
		}
		
		protected function phaseExecutedWithEndGame(request:GameRequest):void
		{
			// DEBUG > Active phase in timeout
			if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "phase {0} executed with END GAME (data:{1})", request.phase, request.result.data);
			// LT_TODO: gestire la causa del game end restituita dal server (forced to GameEndCause.CLIENT_ERROR)
			var cause:GameEndCause = GameEndCause.CLIENT_ERROR;
			// Force the game to end!
			gameEnd(cause);
		}
		
		protected function phaseTimeout(phase:GamePhase):void
		{
			try
			{
				// Check if the current phase is an active phase
				if (phase.active)
				{
					// DEBUG > Active phase in timeout
					if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "phase {0} timeout", phase);
					// Stop to handle the current request
					var request:GameRequest = _client.cancel();
					// Check if the player is attacking
					if (_game.isPlayerAttacking)
					{
						// Increment passed phases counter
						_game.passPhase();
					}
					else
					{
						// LT_TODO: pensare se serve notificare il server o se ce ne battiamo le palle e nel resolve sistemiamo
					}
					// Dispatch GAME_PHASE_TIMEOUT error
					dispatchGamePhaseTimeout(phase);
					// Next phase
					gameNextTurnPhase();
					// Manage next request
					gameNextRequest();
				}
				else
				{
					// ERROR > Timeout cant be handled for a passive phase
					manageGameError("Timeout triggered for a passive phase " + phase);
				}
			} 
			catch(error:Error) 
			{
				// ERROR > Fatal unexpected error
				manageGameError(LOG_MARKER_GAME + "fatal unexpected error in phaseTimeout()", error);
			}
		}
		
		protected function phaseError(phase:GamePhase, code:int, message:String):void
		{
			// DEBUG > Active phase in timeout
			if (Logger.canDebug(DEBUG_KEY_GAME)) Logger.debug(LOG_MARKER_GAME + "phase {0} in error (code:{1}, message: {2})", phase, code, message);
			// Check if the current action 
			if (phase.active)
			{
				// Restarts the timeout timer
				timeoutRestart(phase);
			}
			// Dispatch EVENT_GAME_PHASE_ERROR
			dispatchGamePhaseError(phase, code, message);
		}
		
		
		// Timeout handling
		
		protected function timeoutStart(phase:GamePhase):void
		{
			// Check timeout enabled
			if (_options.timeoutEnabled)
			{
				// Check phase
				if (phase.active)
				{
					// Check previous activation
					if (!_timeoutActive)
					{
						// Setup timeout data
						_timeoutLastDate = new Date();
						_timeoutElapsed = 0;
						_timeoutPhase = phase;
						_timeoutActive = true;
						// DEBUG > Timeout started
						if (Logger.canDebug(DEBUG_KEY_TIMEOUT)) Logger.debug(LOG_MARKER_TIMEOUT + "started! (phase:{0})", phase);					
					}
					else
					{
						// ERROR > Timeout already started!
						manageGameError("Timeout already started!");					
					}
				}
				else
				{
					// ERROR > Phase is passive, timeout could no  be started
					manageGameError("Timeout for phase " + phase + "could not be started");
				}
			}
		}
		
		protected function timeoutRestart(phase:GamePhase):void
		{
			// Check timeout enabled
			if (_options.timeoutEnabled)
			{
				// Phase check
				if (_timeoutPhase == phase)
				{
					// Check previous activation
					if (!_timeoutActive)
					{
						// Raise active flag			
						_timeoutActive = true;
						// DEBUG > Timeout restarted
						if (Logger.canDebug(DEBUG_KEY_TIMEOUT)) Logger.debug(LOG_MARKER_TIMEOUT + "restarted!");					
					}
					else
					{
						// ERROR > Timeout already running!
						manageGameError("Timeout restart requested when is already running!");					
					}
				}
				else
				{
					// ERROR > Phase is passive, timeout could no  be started
					manageGameError("Timeout restart requested for a wrong phase: " + phase + " not " + _timeoutPhase);
				}
			}
		}
		
		protected function timeoutPause(phase:GamePhase):void
		{
			// Check timeout enabled
			if (_options.timeoutEnabled)
			{
				// Phase check
				if (_timeoutPhase == phase)
				{
					// Check previous activation
					if (_timeoutActive)
					{
						// Lowers active flag
						_timeoutActive = false;
						// DEBUG > Timeout restarted
						if (Logger.canDebug(DEBUG_KEY_TIMEOUT)) Logger.debug(LOG_MARKER_TIMEOUT + "paused!");					
					}
					else
					{
						// ERROR > Timeout already running!
						manageGameError("Timeout pause requested when is NOT running!");					
					}
				}
				else
				{
					// ERROR > Phase is passive, timeout could no  be started
					manageGameError("Timeout restart requested for a wrong phase: " + phase + " not " + _timeoutPhase);
				}
			}
		}
		
		protected function timeoutStop():void
		{
			// Check timeout enabled
			if (_options.timeoutEnabled)
			{
				// Reset all timeout status
				_timeoutLastDate = null;
				_timeoutElapsed = 0;
				_timeoutPhase = null;
				_timeoutActive = false;
				// DEBUG > Timeout restarted
				if (Logger.canDebug(DEBUG_KEY_TIMEOUT)) Logger.debug(LOG_MARKER_TIMEOUT + "stopped!");
			}			
		}
		
		
		// Worker event handlers
		
		protected function workerStart():void
		{
			// Stops the worker
			_worker.start();
			// DEBUG > Log worker started
			if (Logger.canDebug(DEBUG_KEY_WORKER)) Logger.debug(LOG_MARKER_WORKER + "started");
		}
		
		protected function workerStop():void
		{
			// Stops the worker
			_worker.stop();
			// DEBUG > Log worker stopped
			if (Logger.canDebug(DEBUG_KEY_WORKER)) Logger.debug(LOG_MARKER_WORKER + "stopped");
		}
		
		protected function workerHandler(e:TimerEvent):void
		{
			try
			{
				// Stops the worker to execute the handler
				workerStop();
				// Check engine status
				switch(_status)
				{
					case GameEngineStatus.IDLE:
					{
						// DEBUG > Client connected
						if (Logger.canDebug(DEBUG_KEY_WORKER)) Logger.debug(LOG_MARKER_WORKER + " engine IDLE, waiting ...");
						break;
					}
						
					case GameEngineStatus.MATCHMAKING:
					{
						// LT_TODO: tutto da definire
						break;
					}
						
					case GameEngineStatus.PLAYING:
					{
						// Get the current phase and check if the phase is active and wait for user interaction
						var phase:GamePhase = GamePhase.getByGameStatus(_game.status);
						if (phase != null && phase.active)
						{
							// Check if the timeout handling for active phases is enabled
							if (_options.timeoutEnabled)
							{
								// Check if the timeout hanling is running 
								if (_timeoutActive)
								{
									// Retrieve the timeout for the phase
									var timeout:uint = _options.getGamePhaseTimeout(phase);
									// CHeck if the timeout is to check
									if (timeout > 0)
									{
										var now:Date = new Date();
										// Retrieve the time elapsed and check timeout
										var elapsed:Number = DateUtils.elapsed(_timeoutLastDate, now);
										if (_timeoutElapsed + elapsed >= timeout)
										{
											// DEBUG > Log worker stopped
											if (Logger.canDebug(DEBUG_KEY_WORKER)) Logger.debug(LOG_MARKER_WORKER + "phase {0} timeout ({1} ms)", phase, timeout);
											// Phase in timeout!
											phaseTimeout(phase);
										}
										else
										{
											// Update the timeout date and elapsed 
											_timeoutElapsed += elapsed;
											_timeoutLastDate = now;
											// Timeout percentage
											var percentage:Number = (elapsed * 100.0) / (timeout * 1.0);
											// Dispatch EVENT_GAME_PHASE_PROGRESS event
											dispatchGamePhaseProgress(phase, elapsed, timeout, percentage);
										}
									}
								}
							}
						}
						break;
					}
				}
				// Starts again the worker
				workerStart();
			} 
			catch(error:Error) 
			{
				// ERROR > Fatal unexpected error
				manageFatalError(LOG_MARKER_WORKER + "fatal unexpected error", error);
			}
		}
		
		
		// Client event handlers
		
		protected function client_ConnectHandler(event:GameClientEvent):void
		{
			try
			{
				// DEBUG > Client connected
				if (Logger.canDebug(DEBUG_KEY_CLIENT)) Logger.debug(LOG_MARKER_CLIENT + "client connected");
				// STATUS > Running
				setStatus(GameEngineStatus.RUNNING);
				// Starts the worker
				workerStart();
				// Dispatch engine started
				dispatchEngineStartEvent();
				// DEBUG > Client connected
				if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "STARTED");
			} 
			catch(error:Error) 
			{
				// ERROR > Fatal unexpected error
				manageFatalError(LOG_MARKER_CLIENT + "fatal unexpected error in client_ConnectHandler()", error);
			}
		}
		
		protected function client_DisconnectHandler(e:GameClientEvent):void
		{
			try
			{
				// DEBUG > Client disconnected
				if (Logger.canDebug(DEBUG_KEY_CLIENT)) Logger.debug(LOG_MARKER_CLIENT + "client disconnected");
			} 
			catch(error:Error) 
			{
				// ERROR > Fatal unexpected error
				manageFatalError(LOG_MARKER_CLIENT + "fatal unexpected error in client_DisconnectHandler()", error);
			}
		}
		
		protected function client_ConnectionFailedHandler(e:GameClientEvent):void
		{
			// DEBUG > Client connection failed
			if (Logger.canDebug(DEBUG_KEY_CLIENT)) Logger.debug(LOG_MARKER_CLIENT + "client connection failed");
			// Connection error handling
			manageClientError("Connection failed!");
		}
		
		protected function client_ConnectionLostHandler(e:GameClientEvent):void
		{
			// DEBUG > Client connection failed
			if (Logger.canDebug(DEBUG_KEY_CLIENT)) Logger.debug(LOG_MARKER_CLIENT + "client connection lost");
			// Connection error handling
			manageClientError("Connection lost!");
		}
		
		protected function client_RequestExecutedHandler(e:GameClientRequestEvent):void
		{
			try
			{
				// DEBUG > Request executed
				if (Logger.canDebug(DEBUG_KEY_CLIENT)) Logger.debug(LOG_MARKER_CLIENT + "{0} request executed (result:{1})", e.request, e.request.result);
				// Request execution handling
				phaseExecuted(e.request);
			} 
			catch(error:Error) 
			{
				// ERROR > Fatal unexpected error
				manageFatalError(LOG_MARKER_CLIENT + "fatal unexpected error in client_RequestExecutedHandler()", error);
			}
		}
		
		protected function client_RequestWaitingHandler(e:GameClientRequestEvent):void
		{
			// DEBUG > Request polling waiting
			if (Logger.canDebug(DEBUG_KEY_CLIENT)) Logger.debug(LOG_MARKER_CLIENT + "{0} in polling", e.request);
		}
		
		protected function client_RequestFailedHandler(e:GameClientRequestEvent):void
		{			
			// DEBUG > Request failed
			if (Logger.canDebug(DEBUG_KEY_CLIENT)) Logger.debug(LOG_MARKER_CLIENT + "{0} request failed (code:{1}, message:{2}", e.request, e.request.result.error.code, e.request.result.error.message);
			// Handle game phase error
			phaseError(e.request.phase, e.request.result.error.code, e.request.result.error.message);
		}
		
		protected function client_ErrorHandler(e:GameClientErrorEvent):void
		{
			// DEBUG > Client error
			if (Logger.canDebug(DEBUG_KEY_CLIENT)) Logger.debug(LOG_MARKER_CLIENT + "client in error:  {0}", e.error.message);
			// ERROR > Client fatal unexpected error
			manageClientError(LOG_MARKER_CLIENT + "fatal  error in client execution", e.error);
		}
		
		
		// Game event dispatchers
		
		protected function dispatchGameStart():void
		{
			dispatchEvent(new GameEvent(GameEvent.EVENT_GAME_START, _game));
		}
		
		protected function dispatchGameRestart():void
		{
			dispatchEvent(new GameEvent(GameEvent.EVENT_GAME_RESTART, _game));
		}
		
		protected function dispatchGameSubstitutionPhase():void
		{
			dispatchEvent(new GamePhaseEvent(GameEvent.EVENT_GAME_SUBSTITUTION_PHASE, _game, GamePhase.SUBSTITUTE));
		}
		
		protected function dispatchGameResolveSubstitutionPhase(playerFormation:GameFormation, opponentFormation:GameFormation):void
		{
			dispatchEvent(new GameResolveSubstitutionPhaseEvent(_game, playerFormation, opponentFormation));
		}
		
		protected function dispatchGameDiscardPhase():void
		{
			dispatchEvent(new GamePhaseEvent(GameEvent.EVENT_GAME_DISCARD_PHASE, _game, GamePhase.DISCARD));
		}
		
		protected function dispatchGameResolveDiscardPhase(playerCards:GameCards, opponentCards:GameCards):void
		{
			dispatchEvent(new GameResolveDiscardPhaseEvent(_game, playerCards, opponentCards));
		}
		
		protected function dispatchGamePlayPhase():void
		{
			dispatchEvent(new GamePhaseEvent(GameEvent.EVENT_GAME_PLAY_PHASE, _game, GamePhase.PLAY));
		}
		
		protected function dispatchGameResolvePlayPhase(turn:GameTurn):void
		{
			dispatchEvent(new GameResolvePlayPhaseEvent(_game, turn));
		}
		
		protected function dispatchGameResyncPhase():void
		{
			dispatchEvent(new GamePhaseEvent(GameEvent.EVENT_GAME_RESYNC_PHASE, _game, GamePhase.RESYNC));
		}
		
		protected function dispatchGameNextTurn(turn:int):void
		{
			dispatchEvent(new GameNextTurnEvent(_game, turn));
		}
		
		protected function dispatchGameNextTurnPhase(phase:GamePhase):void
		{
			dispatchEvent(new GamePhaseEvent(GameEvent.EVENT_GAME_NEXT_TURN_PHASE, _game, phase));
		}
		
		protected function dispatchGameResync():void
		{
			dispatchEvent(new GameEvent(GameEvent.EVENT_GAME_RESYNC, _game));
		}
		
		protected function dispatchGameDesync():void
		{
			// Dispatch EVENT_GAME_DESYNC event
			dispatchEvent(new GameEvent(GameEvent.EVENT_GAME_DESYNC, _game));
		}
		
		protected function dispatchGameEnd(cause:GameEndCause):void
		{
			dispatchEvent(new GameEndEvent(_game, cause));
		}
		
		protected function dispatchGamePhaseError(phase:GamePhase, code:int, message:String):void
		{
			dispatchEvent(new GamePhaseErrorEvent(GameEvent.EVENT_GAME_PHASE_ERROR, _game, phase, code, message));
		}
		
		protected function dispatchGamePhaseProgress(phase:GamePhase, elapsed:Number, timeout: Number, percentage:Number):void
		{
			dispatchEvent(new GamePhaseProgressEvent(_game, phase, elapsed, timeout, percentage));
		}
		
		protected function dispatchGamePhaseTimeout(phase:GamePhase):void
		{
			dispatchEvent(new GamePhaseTimeoutEvent(_game, phase));
		}
		
		protected function dispatchGameError(error:Error):void
		{
			dispatchEvent(new GameErrorEvent(_game, error));
		}
		
		
		// Event dispatchers

		protected function dispatchEngineStartEvent():void
		{
			dispatchEvent(new GameEngineEvent(GameEngineEvent.EVENT_GAME_ENGINE_START, this));
		}
		
		protected function dispatchEngineStopEvent():void
		{
			dispatchEvent(new GameEngineEvent(GameEngineEvent.EVENT_GAME_ENGINE_STOP, this));
		}
		
		protected function dispatchEnginePauseEvent():void
		{
			dispatchEvent(new GameEngineEvent(GameEngineEvent.EVENT_GAME_ENGINE_PAUSE, this));
		}
		
		protected function dispatchEngineResumeEvent():void
		{
			dispatchEvent(new GameEngineEvent(GameEngineEvent.EVENT_GAME_ENGINE_RESUME, this));
		}
		
		protected function dispatchEngineErrorEvent(message:String, error:Error = null):void
		{
			dispatchEvent(new GameEngineErrorEvent(this, message, error));
		}

		protected function dispatchEngineMatchmakingStartEvent():void
		{
			dispatchEvent(new GameEngineEvent(GameEngineEvent.EVENT_GAME_ENGINE_MATCHMAKING_START, this));
		}
		
		protected function dispatchEngineMatchmakingStopEvent():void
		{
			dispatchEvent(new GameEngineEvent(GameEngineEvent.EVENT_GAME_ENGINE_MATCHMAKING_STOP, this));
		}
		
		protected function dispatchEnginePlayStartEvent():void
		{
			dispatchEvent(new GameEngineEvent(GameEngineEvent.EVENT_GAME_ENGINE_PLAY_START, this));
		}
		
		protected function dispatchEnginePlayStopEvent():void
		{
			dispatchEvent(new GameEngineEvent(GameEngineEvent.EVENT_GAME_ENGINE_PLAY_STOP, this));
		}
		
		
		// -------------------------------------------------------------------------------
		// PUBLIC METHODS
		// -------------------------------------------------------------------------------
		
		/**
		 * 
		 * @param phase
		 * @param actionData Dati azione sottoforma di string JSON (opzionale)
		 * @param password Password dell'utente che fa la richiesta (opzionale e da sostituire con il token)
		 * @return
		 * @todo Rimuovere il parametro password, usato attualmente per compatibilità con le pagine php lato server  
		 */
		public function createRequest(phase:GamePhase, actionData:String = null, password:String = null):GameRequest
		{
			// Create request params
			var params:GameRequestParams = new GameRequestParams();
			params.phase 	= phase;
			params.gameID 	= _game.id;
			params.userID 	= _game.player.id.toString();
			params.password = password == null || password == "" ? _client.options.password : password; // LT_TODO: to remove
			params.turn		= _game.turn;
			// apply actionData if provided
			if (actionData != null) params.actionData = actionData; 
			// Create the request
			return GameRequestFactory.create(phase, params);
		}
		
		/**
		 * Executes a request 
		 * @param request Request to execute
		 */		
		public function execute(request:GameRequest):void
		{
			// Check engine status
			if (_status != GameEngineStatus.PLAYING) throw new Error("Can't execute requests, wrong status (status:" + _status + ")");
			// Check already stated
			if (isPlaying && _game.status != GameStatus.READY) 
			{
				// Check if game phase is the same of the request phase
				if (request.phase != GamePhase.getByGameStatus(_game.status)) throw new Error("Can't execute requests, wrong phase (phase:" + request.phase + ")");
				// Pause the timeout handling (without cancel the start date)
				if (request.phase.active) timeoutPause(request.phase);
			}
			// Execute the request with the client
			_client.execute(request);
			// DEBUG > Client connected
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "client executing request {0}", request);
		}
		
		/**
		 * 
		 */		
		public function start():void
		{
			// Check engine status
			if (_status != GameEngineStatus.IDLE) throw new Error("Can't start engine, already running (status:" + _status + ")");			
			// DEBUG > Engine stopped
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "STARTING ...");
			// Connects the client
			_client.connect();
		}
		
		/**
		 * Pauses the engine: used to interrupt the worker and all the
		 * related events
		 */		
		public function pause():void
		{
			// Check engine status
			if (_status == GameEngineStatus.IDLE) throw new Error("Can't pause engine, wrong status (status:" + _status + ")");
			// STATUS > Matchamking
			setStatus(GameEngineStatus.PAUSED);
			// Dispatch engine paused
			dispatchEnginePauseEvent();
			// DEBUG > Engine stopped
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "PAUSED!");
		}
		
		/**
		 * Pauses the engine: used to interrupt the worker and all the
		 * related events
		 */
		public function resume():void
		{
			// Check engine status
			if (_status != GameEngineStatus.PAUSED) throw new Error("Can't resume engine, not paused (status:" + _status + ")");
			// Check if timeout handling is active 
			if (_timeoutActive)
			{
				// Update the the last check date 
				_timeoutLastDate = new Date();
			}
			// STATUS > Restore previous status
			setStatus(_previousStatus);
			// Dispatch engine resumed
			dispatchEngineResumeEvent();
			// DEBUG > Engine stopped
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "RESUMED! status: {0}", _status);
		}
		
		/**
		 * Stop the engine, breaking all current operation and the game 
		 */		
		public function stop():void
		{
			// Check engine status
			// if (_status == GameEngineStatus.IDLE) throw new Error("Can't stop engine, not running (status:" + _status + ")");
			// Stops the worker if running
			if (_worker.running) workerStop();
			// Check if we are in matchmaking
			if (_status == GameEngineStatus.MATCHMAKING)
			{
				// Stops the matchmaking
				stopMatchMacking();
			}
			// Check if the game is running
			if (_status == GameEngineStatus.PLAYING)
			{
				// Stops the game
				stopPlaying();
			}
			// Disconnects the client
			if (_client.isConnected || _client.isConnecting) _client.disconnect();
			// STATUS > Matchamking
			setStatus(GameEngineStatus.IDLE);
			// Dispatch engine stopped
			dispatchEngineStopEvent();
			// DEBUG > Engine stopped
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "STOPPED");
		}
		
		/**
		 * Starts the matchmacking for the given User, creates and empty game and waith for amtchmaking server response
		 * @param user User to start the matchmaking for
		 * @param deck Desc chosen by the User, to use to start a new Game by the matchmaking process 
		 */		
		public function startMatchMacking(user:User, deck:Deck):void
		{
			// Check engine status
			if (_status != GameEngineStatus.RUNNING) throw new Error("Can't start engine matchmaking, wrong status (status:" + _status + ")");
			// DEBUG > Matchmacking start requested
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "matchmaking start requested!");
			// Setup user data
			_user = user;
			_deck = deck;
			// Create a new empty Game
			_game = new Game(_user, _options, null, _deck);
			// GAME > Status WAITING
			_game.changeStatus(GameStatus.WAITING);
			// STATUS > Matchmaking
			setStatus(GameEngineStatus.MATCHMAKING);
			// Dispatch EVENT_GAME_ENGINE_MATCHMAKING_START event 
			dispatchEngineMatchmakingStartEvent();
			// LT_TODO: realizzare la request di matchmaking (polling, passive)
			// execute(machmakingRequest);
		}
		
		/**
		 * Stops the matchmacking process, destroyng the current wainting Game 
		 */		
		public function stopMatchMacking():void
		{
			// Check engine status
			if (_status != GameEngineStatus.MATCHMAKING) throw new Error("Can't start engine matchmaking, wrong status (status:" + _status + ")");
			
			// LT_TODO: forza uscita dal matchmaking _client.cancel();
			
			// Resets the game reference
			if (hasGame) _game != null;
			
			// STATUS > Running
			setStatus(GameEngineStatus.RUNNING);
			// Dispatch EVENT_GAME_ENGINE_MATCHMAKING_STOP event 
			dispatchEngineMatchmakingStopEvent();
			// DEBUG > Engine stopped
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "matchmaking stopped!");
		}
		
		/**
		 *  Starts a Game ready to be started
		 */		
		public function startPlaying():void
		{
			// Check engine status
			if (_status != GameEngineStatus.MATCHMAKING) throw new Error("Can't start game, wrong status (status:" + _status + ")");
			// DEBUG > Game start requested
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "game start requested!");
			// Create the RESYNC request
			var resync:GameRequest = createRequest(GamePhase.RESYNC);
			// STATUS > Playing
			setStatus(GameEngineStatus.PLAYING);
			// Dispatch EVENT_GAME_ENGINE_PLAY_START event
			dispatchEnginePlayStartEvent();
			// Executes the first resync
			execute(resync);
		}
		
		/**
		 *  Stops a Game already started
		 */		
		public function stopPlaying():void
		{
			// Check engine status
			if (_status != GameEngineStatus.PLAYING) throw new Error("Can't start game, not playing (status:" + _status + ")");
			// Stops any timeout
			timeoutStop();
			// Check if the game is currently in error
			if (_game.isInError && !_game.isEnded)
			{
				// Force game end cause
				gameEnd(GameEndCause.CLIENT_ERROR);
			}
			// Resets the game reference
			if (hasGame) _game = null;
			
			// LT_TODO: forza uscita dal game, gestire causa e notificare in qualche modo il server
			
			// STATUS > Running
			setStatus(GameEngineStatus.RUNNING);
			// Dispatch EVENT_GAME_ENGINE_PLAY_STOP event
			dispatchEnginePlayStopEvent();
			// DEBUG > Engine stopped
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "game stopped!");
		}
		
		/**
		 * Debug method to prevent matchmacking, starts a game directly using a valid gameID
		 * LT_TODO: to remove DEBUG ONLY 
		 * @param gameId
		 */
		public function forceStartPlaying(user:User, opponent:Player, deck:Deck, gameID:uint):void
		{
			// Setup user data
			_user = user;
			_deck = deck;
			// Create a new empty Game
			_game = new Game(_user, _options, null, _deck);
			_game.id = gameID;
			_game.opponent = opponent;
			// Force status READY (to avoid errors)
			_game.changeStatus(GameStatus.READY);
			// Force status MATCHMAKING
			setStatus(GameEngineStatus.MATCHMAKING);
			// DEBUG > Game start requested
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "game start FORCED!");
			// Force game start
			startPlaying();
		}
		
	}
}